/**
 * ***************************************************************************** Copyright (c) 2004,
 * 2012 IBM Corporation and others. All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * <p>Contributors: IBM Corporation - initial API and implementation
 * *****************************************************************************
 */
package org.eclipse.jdt.internal.core;

import static org.eclipse.jdt.internal.core.JavaModelManager.PerProjectInfo;
import static org.eclipse.jdt.internal.core.JavaModelManager.getJavaModelManager;
import static org.eclipse.jdt.internal.core.JavaModelManager.getTarget;
import static org.eclipse.jdt.internal.core.JavaModelManager.isFile;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.che.api.project.shared.Constants;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModelStatus;
import org.eclipse.jdt.core.IJavaModelStatusConstants;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IRegion;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.eval.IEvaluationContext;
import org.eclipse.jdt.internal.compiler.util.ObjectVector;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.util.JavaElementFinder;
import org.eclipse.jdt.internal.core.util.MementoTokenizer;
import org.eclipse.jdt.internal.core.util.Messages;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.osgi.service.prefs.BackingStoreException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/** @author Evgen Vidolob */
public class JavaProject extends Openable implements IJavaProject, SuffixConstants {
  /** Name of file containing project classpath */
  public static final String INNER_DIR = Constants.CHE_DIR;

  public static final String CLASSPATH_FILENAME = INNER_DIR + "/classpath";

  /** Whether the underlying file system is case sensitive. */
  protected static final boolean IS_CASE_SENSITIVE = !new File("Temp").equals(new File("temp"));
  /** An empty array of strings indicating that a project doesn't have any prerequesite projects. */
  protected static final String[] NO_PREREQUISITES = CharOperation.NO_STRINGS;

  /** Value of the project's raw classpath if the .classpath file contains invalid entries. */
  public static final IClasspathEntry[] INVALID_CLASSPATH = new IClasspathEntry[0];

  private static final Logger LOG = LoggerFactory.getLogger(JavaProject.class);

  protected IProject project;

  public JavaProject(IProject project, JavaElement parent) {
    super(parent);
    this.project = project;

    // create
    if (project.exists()) {
      IFolder folder = project.getFolder(INNER_DIR);
      if (!folder.exists()) {
        try {
          folder.create(true, true, null);
        } catch (CoreException e) {
          JavaPlugin.log(e);
        }
      }
    }
  }

  /**
   * Returns true if the given project is accessible and it has a java nature, otherwise false.
   *
   * @param project IProject
   * @return boolean
   */
  public static boolean hasJavaNature(IProject project) {
    try {
      return project.exists() && project.hasNature(JavaCore.NATURE_ID);
    } catch (CoreException e) {
      if (ExternalJavaProject.EXTERNAL_PROJECT_NAME.equals(project.getName())) return true;
      // project does not exist or is not open
    }
    return false;
  }

  public static boolean areClasspathsEqual(
      IClasspathEntry[] firstClasspath,
      IClasspathEntry[] secondClasspath,
      IPath firstOutputLocation,
      IPath secondOutputLocation) {
    int length = firstClasspath.length;
    if (length != secondClasspath.length) return false;
    for (int i = 0; i < length; i++) {
      if (!firstClasspath[i].equals(secondClasspath[i])) return false;
    }
    if (firstOutputLocation == null) return secondOutputLocation == null;
    return firstOutputLocation.equals(secondOutputLocation);
  }

  /**
   * Computes the package fragment roots identified by the given entry. Only works with resolved
   * entry
   *
   * @param resolvedEntry IClasspathEntry
   * @return IPackageFragmentRoot[]
   */
  public IPackageFragmentRoot[] computePackageFragmentRoots(IClasspathEntry resolvedEntry) {
    try {
      return computePackageFragmentRoots(
          new IClasspathEntry[] {resolvedEntry},
          false, // don't retrieve exported roots
          null /* no reverse map */);
    } catch (JavaModelException e) {
      return new IPackageFragmentRoot[] {};
    }
  }

  /**
   * Returns the raw classpath for the project, as a list of classpath entries. This corresponds to
   * the exact set of entries which were assigned using <code>setRawClasspath</code>, in particular
   * such a classpath may contain classpath variable and classpath container entries. Classpath
   * variable and classpath container entries can be resolved using the helper method <code>
   * getResolvedClasspath</code>; classpath variable entries also can be resolved individually using
   * <code>JavaCore#getClasspathVariable</code>).
   *
   * <p>Both classpath containers and classpath variables provides a level of indirection that can
   * make the <code>.classpath</code> file stable across workspaces. As an example, classpath
   * variables allow a classpath to no longer refer directly to external JARs located in some user
   * specific location. The classpath can simply refer to some variables defining the proper
   * locations of these external JARs. Similarly, classpath containers allows classpath entries to
   * be computed dynamically by the plug-in that defines that kind of classpath container.
   *
   * <p>Note that in case the project isn't yet opened, the classpath will be read directly from the
   * associated <tt>.classpath</tt> file.
   *
   * @return the raw classpath for the project, as a list of classpath entries
   * @throws org.eclipse.jdt.core.JavaModelException if this element does not exist or if an
   *     exception occurs while accessing its corresponding resource
   * @see org.eclipse.jdt.core.IClasspathEntry
   */
  public IClasspathEntry[] getRawClasspath() throws JavaModelException {
    PerProjectInfo perProjectInfo = getPerProjectInfo();
    IClasspathEntry[] classpath = perProjectInfo.rawClasspath;
    if (classpath != null) return classpath;

    classpath = perProjectInfo.readAndCacheClasspath(this)[0];

    if (classpath == JavaProject.INVALID_CLASSPATH) return defaultClasspath();

    return classpath;
  }

  @Override
  public String[] getRequiredProjectNames() throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public IClasspathEntry[] getResolvedClasspath(boolean ignoreUnresolvedEntry)
      throws JavaModelException {
    return getResolvedClasspath();
  }

  @Override
  public boolean hasBuildState() {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean hasClasspathCycle(IClasspathEntry[] entries) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean isOnClasspath(IJavaElement element) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean isOnClasspath(IResource resource) {
    throw new UnsupportedOperationException();
  }

  @Override
  public IEvaluationContext newEvaluationContext() {
    throw new UnsupportedOperationException();
  }

  @Override
  public ITypeHierarchy newTypeHierarchy(IRegion region, IProgressMonitor monitor)
      throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public ITypeHierarchy newTypeHierarchy(
      IRegion region, WorkingCopyOwner owner, IProgressMonitor monitor) throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public ITypeHierarchy newTypeHierarchy(IType type, IRegion region, IProgressMonitor monitor)
      throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public ITypeHierarchy newTypeHierarchy(
      IType type, IRegion region, WorkingCopyOwner owner, IProgressMonitor monitor)
      throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public IPath readOutputLocation() {
    throw new UnsupportedOperationException();
  }

  @Override
  public IClasspathEntry[] readRawClasspath() {
    throw new UnsupportedOperationException();
  }

  @Override
  public void setOption(String optionName, String optionValue) {
    // Store option value
    IEclipsePreferences projectPreferences = getEclipsePreferences();
    boolean modified =
        getJavaModelManager().storePreference(optionName, optionValue, projectPreferences, null);

    // Write changes
    if (modified) {
      try {
        projectPreferences.flush();
      } catch (BackingStoreException e) {
        // problem with pref store - quietly ignore
      }
    }
    getJavaModelManager().resetProjectOptions(JavaProject.this);
    JavaProject.this.resetCaches(); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=233568
  }

  @Override
  public void setOptions(Map newOptions) {

    IEclipsePreferences projectPreferences = getEclipsePreferences();
    if (projectPreferences == null) return;
    try {
      if (newOptions == null) {
        projectPreferences.clear();
      } else {
        Iterator entries = newOptions.entrySet().iterator();
        JavaModelManager javaModelManager = getJavaModelManager();
        while (entries.hasNext()) {
          Map.Entry entry = (Map.Entry) entries.next();
          String key = (String) entry.getKey();
          String value = (String) entry.getValue();
          javaModelManager.storePreference(key, value, projectPreferences, newOptions);
        }

        // reset to default all options not in new map
        // @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=26255
        // @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=49691
        String[] pNames = projectPreferences.keys();
        int ln = pNames.length;
        for (int i = 0; i < ln; i++) {
          String key = pNames[i];
          if (!newOptions.containsKey(key)) {
            projectPreferences.remove(key); // old preferences => remove from preferences table
          }
        }
      }

      // persist options
      projectPreferences.flush();

      // flush cache immediately
      try {
        getPerProjectInfo().options = null;
      } catch (JavaModelException e) {
        // do nothing
      }
    } catch (BackingStoreException e) {
      // problem with pref store - quietly ignore
    }
  }

  @Override
  public void setOutputLocation(IPath path, IProgressMonitor monitor) throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void setRawClasspath(
      IClasspathEntry[] entries,
      IPath outputLocation,
      boolean canModifyResources,
      IProgressMonitor monitor)
      throws JavaModelException {
    setRawClasspath(entries, null, outputLocation, canModifyResources, monitor);
  }

  @Override
  public void setRawClasspath(
      IClasspathEntry[] entries, boolean canModifyResources, IProgressMonitor monitor)
      throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void setRawClasspath(
      IClasspathEntry[] entries,
      IClasspathEntry[] referencedEntries,
      IPath outputLocation,
      IProgressMonitor monitor)
      throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public IClasspathEntry[] getReferencedClasspathEntries() throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void setRawClasspath(IClasspathEntry[] entries, IProgressMonitor monitor)
      throws JavaModelException {
    setRawClasspath(
        entries,
        //                getOutputLocation()/*don't change output*/,
        null,
        true /*can change resource (as per API contract)*/,
        monitor);
  }

  @Override
  public void setRawClasspath(
      IClasspathEntry[] entries, IPath outputLocation, IProgressMonitor monitor)
      throws JavaModelException {
    setRawClasspath(
        entries, outputLocation, true /*can change resource (as per API contract)*/, monitor);
  }

  protected void setRawClasspath(
      IClasspathEntry[] newRawClasspath,
      IClasspathEntry[] referencedEntries,
      IPath newOutputLocation,
      boolean canModifyResources,
      IProgressMonitor monitor)
      throws JavaModelException {

    try {
      if (newRawClasspath == null) // are we already with the default classpath
      newRawClasspath = defaultClasspath();

      SetClasspathOperation op =
          new SetClasspathOperation(
              this, newRawClasspath, referencedEntries, newOutputLocation, canModifyResources);
      op.runOperation(monitor);
    } catch (JavaModelException e) {
      getJavaModelManager().getDeltaProcessor().flush();
      throw e;
    }
  }

  public String[] projectPrerequisites(IClasspathEntry[] resolvedClasspath)
      throws JavaModelException {

    ArrayList prerequisites = new ArrayList();
    for (int i = 0, length = resolvedClasspath.length; i < length; i++) {
      IClasspathEntry entry = resolvedClasspath[i];
      if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
        prerequisites.add(entry.getPath().lastSegment());
      }
    }
    int size = prerequisites.size();
    if (size == 0) {
      return NO_PREREQUISITES;
    } else {
      String[] result = new String[size];
      prerequisites.toArray(result);
      return result;
    }
  }
  /**
   * This is a helper method returning the resolved classpath for the project as a list of simple
   * (non-variable, non-container) classpath entries. All classpath variable and classpath container
   * entries in the project's raw classpath will be replaced by the simple classpath entries they
   * resolve to.
   *
   * <p>The resulting resolved classpath is accurate for the given point in time. If the project's
   * raw classpath is later modified, or if classpath variables are changed, the resolved classpath
   * can become out of date. Because of this, hanging on resolved classpath is not recommended.
   *
   * <p>Note that if the resolution creates duplicate entries (i.e. {@link IClasspathEntry entries}
   * which are {@link Object#equals(Object)}), only the first one is added to the resolved
   * classpath.
   *
   * @see IClasspathEntry
   */
  public IClasspathEntry[] getResolvedClasspath() throws JavaModelException {
    PerProjectInfo perProjectInfo = getPerProjectInfo();
    IClasspathEntry[] resolvedClasspath = perProjectInfo.getResolvedClasspath();
    if (resolvedClasspath == null) {
      resolveClasspath(
          perProjectInfo,
          false /*don't use previous session values*/,
          true /*add classpath change*/);
      resolvedClasspath = perProjectInfo.getResolvedClasspath();
      if (resolvedClasspath == null) {
        // another thread reset the resolved classpath, use a temporary PerProjectInfo
        PerProjectInfo temporaryInfo = newTemporaryInfo();
        resolveClasspath(
            temporaryInfo,
            false /*don't use previous session values*/,
            true /*add classpath change*/);
        resolvedClasspath = temporaryInfo.getResolvedClasspath();
      }
    }
    return resolvedClasspath;
  }
  /*
   * Resolve the given perProjectInfo's raw classpath and store the resolved classpath in the perProjectInfo.
   */
  public void resolveClasspath(
      PerProjectInfo perProjectInfo, boolean usePreviousSession, boolean addClasspathChange)
      throws JavaModelException {

    JavaModelManager manager = getJavaModelManager();
    boolean isClasspathBeingResolved = manager.isClasspathBeingResolved(this);
    try {
      if (!isClasspathBeingResolved) {
        manager.setClasspathBeingResolved(this, true);
      }

      // get raw info inside a synchronized block to ensure that it is consistent
      IClasspathEntry[][] classpath = new IClasspathEntry[2][];
      int timeStamp;
      synchronized (perProjectInfo) {
        classpath[0] = perProjectInfo.rawClasspath;
        classpath[1] = perProjectInfo.referencedEntries;
        // Checking null only for rawClasspath enough
        if (classpath[0] == null) classpath = perProjectInfo.readAndCacheClasspath(this);
        timeStamp = perProjectInfo.rawTimeStamp;
      }

      ResolvedClasspath result =
          resolveClasspath(
              classpath[0], classpath[1], usePreviousSession, true /*resolve chained libraries*/);

      // store resolved info along with the raw info to ensure consistency
      perProjectInfo.setResolvedClasspath(
          result.resolvedClasspath,
          result.referencedEntries,
          result.rawReverseMap,
          result.rootPathToResolvedEntries,
          usePreviousSession ? PerProjectInfo.NEED_RESOLUTION : result.unresolvedEntryStatus,
          timeStamp,
          addClasspathChange);
    } finally {
      if (!isClasspathBeingResolved) {
        manager.setClasspathBeingResolved(this, false);
      }
    }
  }

  public ResolvedClasspath resolveClasspath(
      IClasspathEntry[] rawClasspath,
      IClasspathEntry[] referencedEntries,
      boolean usePreviousSession,
      boolean resolveChainedLibraries)
      throws JavaModelException {
    JavaModelManager manager = getJavaModelManager();
    //        ExternalFoldersManager externalFoldersManager = JavaModelManager.getExternalManager();
    ResolvedClasspath result = new ResolvedClasspath();
    Map knownDrives = new HashMap();

    Map referencedEntriesMap = new HashMap();
    List rawLibrariesPath = new ArrayList();
    LinkedHashSet resolvedEntries = new LinkedHashSet();

    if (resolveChainedLibraries) {
      for (int index = 0; index < rawClasspath.length; index++) {
        IClasspathEntry currentEntry = rawClasspath[index];
        if (currentEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
          rawLibrariesPath.add(
              ClasspathEntry.resolveDotDot(getProject().getLocation(), currentEntry.getPath()));
        }
      }
      if (referencedEntries != null) {
        // The Set is required to keep the order intact while the referencedEntriesMap (Map)
        // is used to map the referenced entries with path
        LinkedHashSet referencedEntriesSet = new LinkedHashSet();
        for (int index = 0; index < referencedEntries.length; index++) {
          IPath path = referencedEntries[index].getPath();
          if (!rawLibrariesPath.contains(path) && referencedEntriesMap.get(path) == null) {
            referencedEntriesMap.put(path, referencedEntries[index]);
            referencedEntriesSet.add(referencedEntries[index]);
          }
        }
        if (referencedEntriesSet.size() > 0) {
          result.referencedEntries = new IClasspathEntry[referencedEntriesSet.size()];
          referencedEntriesSet.toArray(result.referencedEntries);
        }
      }
    }

    int length = rawClasspath.length;
    for (int i = 0; i < length; i++) {

      IClasspathEntry rawEntry = rawClasspath[i];
      IClasspathEntry resolvedEntry = rawEntry;

      switch (rawEntry.getEntryKind()) {
        case IClasspathEntry.CPE_VARIABLE:
          try {
            resolvedEntry = manager.resolveVariableEntry(rawEntry, usePreviousSession);
          } catch (ClasspathEntry.AssertionFailedException e) {
            // Catch the assertion failure and set status instead
            // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=55992
            result.unresolvedEntryStatus =
                new JavaModelStatus(IJavaModelStatusConstants.INVALID_PATH, e.getMessage());
            break;
          }
          if (resolvedEntry == null) {
            result.unresolvedEntryStatus =
                new JavaModelStatus(
                    IJavaModelStatusConstants.CP_VARIABLE_PATH_UNBOUND, this, rawEntry.getPath());
          } else {
            // If the entry is already present in the rawReversetMap, it means the entry and the
            // chained libraries
            // have already been processed. So, skip it.
            if (resolveChainedLibraries
                && resolvedEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY
                && result.rawReverseMap.get(resolvedEntry.getPath()) == null) {
              // resolve Class-Path: in manifest
              ClasspathEntry[] extraEntries =
                  ((ClasspathEntry) resolvedEntry).resolvedChainedLibraries();
              for (int j = 0, length2 = extraEntries.length; j < length2; j++) {
                if (!rawLibrariesPath.contains(extraEntries[j].getPath())) {
                  // https://bugs.eclipse.org/bugs/show_bug.cgi?id=305037
                  // referenced entries for variable entries could also be persisted with extra
                  // attributes, so addAsChainedEntry = true
                  addToResult(
                      rawEntry,
                      extraEntries[j],
                      result,
                      resolvedEntries, /*externalFoldersManager*/
                      referencedEntriesMap,
                      true,
                      knownDrives);
                }
              }
            }
            addToResult(
                rawEntry,
                resolvedEntry,
                result,
                resolvedEntries, /*externalFoldersManager,*/
                referencedEntriesMap,
                false,
                knownDrives);
          }
          break;

        case IClasspathEntry.CPE_CONTAINER:
          IClasspathContainer container =
              usePreviousSession
                  ? manager.getPreviousSessionContainer(rawEntry.getPath(), this)
                  : JavaCore.getClasspathContainer(rawEntry.getPath(), this);
          if (container == null) {
            result.unresolvedEntryStatus =
                new JavaModelStatus(
                    IJavaModelStatusConstants.CP_CONTAINER_PATH_UNBOUND, this, rawEntry.getPath());
            break;
          }

          IClasspathEntry[] containerEntries = container.getClasspathEntries();
          if (containerEntries == null) {
            if (JavaModelManager.CP_RESOLVE_VERBOSE
                || JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) {
              //
              // JavaModelManager.getJavaModelManager().verbose_missbehaving_container_null_entries(this, rawEntry.getPath());
            }
            break;
          }

          // container was bound
          for (int j = 0, containerLength = containerEntries.length; j < containerLength; j++) {
            ClasspathEntry cEntry = (ClasspathEntry) containerEntries[j];
            if (cEntry == null) {
              if (JavaModelManager.CP_RESOLVE_VERBOSE
                  || JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) {
                //
                // JavaModelManager.getJavaModelManager().verbose_missbehaving_container(this,
                // rawEntry.getPath(), containerEntries);
              }
              break;
            }
            // if container is exported or restricted, then its nested entries must in turn be
            // exported  (21749) and/or propagate restrictions
            cEntry = cEntry.combineWith((ClasspathEntry) rawEntry);

            if (cEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
              // resolve ".." in library path
              cEntry = cEntry.resolvedDotDot(getProject().getLocation());
              // https://bugs.eclipse.org/bugs/show_bug.cgi?id=313965
              // Do not resolve if the system attribute is set to false
              if (resolveChainedLibraries
                  && getJavaModelManager().resolveReferencedLibrariesForContainers
                  && result.rawReverseMap.get(cEntry.getPath()) == null) {
                // resolve Class-Path: in manifest
                ClasspathEntry[] extraEntries = cEntry.resolvedChainedLibraries();
                for (int k = 0, length2 = extraEntries.length; k < length2; k++) {
                  if (!rawLibrariesPath.contains(extraEntries[k].getPath())) {
                    addToResult(
                        rawEntry,
                        extraEntries[k],
                        result,
                        resolvedEntries, /*externalFoldersManager,*/
                        referencedEntriesMap,
                        false,
                        knownDrives);
                  }
                }
              }
            }
            addToResult(
                rawEntry,
                cEntry,
                result,
                resolvedEntries, /*externalFoldersManager,*/
                referencedEntriesMap,
                false,
                knownDrives);
          }
          break;

        case IClasspathEntry.CPE_LIBRARY:
          // resolve ".." in library path
          resolvedEntry = ((ClasspathEntry) rawEntry).resolvedDotDot(getProject().getLocation());

          if (resolveChainedLibraries
              && result.rawReverseMap.get(resolvedEntry.getPath()) == null) {
            // resolve Class-Path: in manifest
            ClasspathEntry[] extraEntries =
                ((ClasspathEntry) resolvedEntry).resolvedChainedLibraries();
            for (int k = 0, length2 = extraEntries.length; k < length2; k++) {
              if (!rawLibrariesPath.contains(extraEntries[k].getPath())) {
                addToResult(
                    rawEntry,
                    extraEntries[k],
                    result,
                    resolvedEntries, /*externalFoldersManager,*/
                    referencedEntriesMap,
                    true,
                    knownDrives);
              }
            }
          }

          addToResult(
              rawEntry,
              resolvedEntry,
              result,
              resolvedEntries, /*externalFoldersManager,*/
              referencedEntriesMap,
              false,
              knownDrives);
          break;
        default:
          addToResult(
              rawEntry,
              resolvedEntry,
              result,
              resolvedEntries, /* externalFoldersManager,*/
              referencedEntriesMap,
              false,
              knownDrives);
          break;
      }
    }
    result.resolvedClasspath = new IClasspathEntry[resolvedEntries.size()];
    resolvedEntries.toArray(result.resolvedClasspath);
    return result;
  }

  /*
   * Returns a PerProjectInfo that doesn't register classpath change
   * and that should be used as a temporary info.
   */
  public PerProjectInfo newTemporaryInfo() {
    return new PerProjectInfo(this.project.getProject()) {
      protected ClasspathChange addClasspathChange() {
        return null;
      }
    };
  }

  /**
   * Returns the classpath entry that refers to the given path or <code>null</code> if there is no
   * reference to the path.
   *
   * @param path IPath
   * @return IClasspathEntry
   * @throws JavaModelException
   */
  public IClasspathEntry getClasspathEntryFor(IPath path) throws JavaModelException {
    getResolvedClasspath(); // force resolution
    PerProjectInfo perProjectInfo = getPerProjectInfo();
    if (perProjectInfo == null) return null;
    Map rootPathToResolvedEntries = perProjectInfo.rootPathToResolvedEntries;
    if (rootPathToResolvedEntries == null) return null;
    IClasspathEntry classpathEntry = (IClasspathEntry) rootPathToResolvedEntries.get(path);
    if (classpathEntry == null) {
      path = getProject().getWorkspace().getRoot().getLocation().append(path);
      classpathEntry = (IClasspathEntry) rootPathToResolvedEntries.get(path);
    }
    return classpathEntry;
  }

  @Override
  public IJavaElement findElement(IPath path) throws JavaModelException {
    return findElement(path, DefaultWorkingCopyOwner.PRIMARY);
  }

  @Override
  public IJavaElement findElement(IPath path, WorkingCopyOwner owner) throws JavaModelException {
    if (path == null || path.isAbsolute()) {
      throw new JavaModelException(
          new JavaModelStatus(IJavaModelStatusConstants.INVALID_PATH, path));
    }
    try {

      String extension = path.getFileExtension();
      if (extension == null) {
        String packageName = path.toString().replace(IPath.SEPARATOR, '.');
        return findPackageFragment(packageName);
      } else if (Util.isJavaLikeFileName(path.lastSegment())
          || extension.equalsIgnoreCase(SuffixConstants.EXTENSION_class)) {
        IPath packagePath = path.removeLastSegments(1);
        String packageName = packagePath.toString().replace(IPath.SEPARATOR, '.');
        String typeName = path.lastSegment();
        typeName = typeName.substring(0, typeName.length() - extension.length() - 1);
        String qualifiedName = null;
        if (packageName.length() > 0) {
          qualifiedName = packageName + "." + typeName; // $NON-NLS-1$
        } else {
          qualifiedName = typeName;
        }

        // lookup type
        NameLookup lookup = newNameLookup(owner);
        NameLookup.Answer answer =
            lookup.findType(
                qualifiedName,
                false,
                NameLookup.ACCEPT_ALL,
                true /* consider secondary types */,
                false /* do NOT wait for indexes */,
                false /*don't check restrictions*/,
                null);

        if (answer != null) {
          return answer.type.getParent();
        } else {
          return null;
        }
      } else {
        // unsupported extension
        return null;
      }
    } catch (JavaModelException e) {
      if (e.getStatus().getCode() == IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST) {
        return null;
      } else {
        throw e;
      }
    }
  }

  @Override
  public IJavaElement findElement(String bindingKey, WorkingCopyOwner owner)
      throws JavaModelException {
    JavaElementFinder elementFinder = new JavaElementFinder(bindingKey, this, owner);
    elementFinder.parse();
    if (elementFinder.exception != null) throw elementFinder.exception;
    return elementFinder.element;
  }

  public IJavaElement findPackageFragment(String packageName) throws JavaModelException {
    NameLookup lookup =
        newNameLookup((WorkingCopyOwner) null /*no need to look at working copies for pkgs*/);
    IPackageFragment[] pkgFragments = lookup.findPackageFragments(packageName, false);
    if (pkgFragments == null) {
      return null;

    } else {
      // try to return one that is a child of this project
      for (int i = 0, length = pkgFragments.length; i < length; i++) {

        IPackageFragment pkgFragment = pkgFragments[i];
        if (equals(pkgFragment.getParent().getParent())) {
          return pkgFragment;
        }
      }
      // default to the first one
      return pkgFragments[0];
    }
  }

  @Override
  public IPackageFragment findPackageFragment(IPath path) throws JavaModelException {
    return findPackageFragment0(path);
  }

  private IPackageFragment findPackageFragment0(IPath path) throws JavaModelException {

    NameLookup lookup =
        newNameLookup((WorkingCopyOwner) null /*no need to look at working copies for pkgs*/);
    return lookup.findPackageFragment(path);
  }

  @Override
  public IPackageFragmentRoot findPackageFragmentRoot(IPath path) throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public IPackageFragmentRoot[] findPackageFragmentRoots(IClasspathEntry entry) {
    throw new UnsupportedOperationException();
  }

  @Override
  public IType findType(String fullyQualifiedName) throws JavaModelException {
    return findType(fullyQualifiedName, DefaultWorkingCopyOwner.PRIMARY);
  }

  @Override
  public IType findType(String fullyQualifiedName, IProgressMonitor progressMonitor)
      throws JavaModelException {
    return findType(fullyQualifiedName, DefaultWorkingCopyOwner.PRIMARY, progressMonitor);
  }

  /*
   * Internal findType with instanciated name lookup
   */
  IType findType(
      String fullyQualifiedName,
      NameLookup lookup,
      boolean considerSecondaryTypes,
      IProgressMonitor progressMonitor)
      throws JavaModelException {
    NameLookup.Answer answer =
        lookup.findType(
            fullyQualifiedName,
            false,
            org.eclipse.jdt.internal.core.NameLookup.ACCEPT_ALL,
            considerSecondaryTypes,
            true, /* wait for indexes (only if consider secondary types)*/
            false /*don't check restrictions*/,
            progressMonitor);
    if (answer == null) {
      // try to find enclosing type
      int lastDot = fullyQualifiedName.lastIndexOf('.');
      if (lastDot == -1) return null;
      IType type =
          findType(
              fullyQualifiedName.substring(0, lastDot),
              lookup,
              considerSecondaryTypes,
              progressMonitor);
      if (type != null) {
        type = type.getType(fullyQualifiedName.substring(lastDot + 1));
        if (!type.exists()) {
          return null;
        }
      }
      return type;
    }
    return answer.type;
  }

  @Override
  public IType findType(String fullyQualifiedName, WorkingCopyOwner owner)
      throws JavaModelException {
    NameLookup lookup = newNameLookup(owner);
    return findType(fullyQualifiedName, lookup, false, null);
  }

  @Override
  public IType findType(
      String fullyQualifiedName, WorkingCopyOwner owner, IProgressMonitor progressMonitor)
      throws JavaModelException {
    NameLookup lookup = newNameLookup(owner);
    return findType(fullyQualifiedName, lookup, true, progressMonitor);
  }

  @Override
  public IType findType(String packageName, String typeQualifiedName) throws JavaModelException {
    return findType(packageName, typeQualifiedName, DefaultWorkingCopyOwner.PRIMARY);
  }

  @Override
  public IType findType(
      String packageName, String typeQualifiedName, IProgressMonitor progressMonitor)
      throws JavaModelException {
    return findType(
        packageName, typeQualifiedName, DefaultWorkingCopyOwner.PRIMARY, progressMonitor);
  }

  @Override
  public IType findType(String packageName, String typeQualifiedName, WorkingCopyOwner owner)
      throws JavaModelException {
    NameLookup lookup = newNameLookup(owner);
    return findType(
        packageName,
        typeQualifiedName,
        lookup,
        false, // do not consider secondary types
        null);
  }

  @Override
  public IType findType(
      String packageName,
      String typeQualifiedName,
      WorkingCopyOwner owner,
      IProgressMonitor progressMonitor)
      throws JavaModelException {
    NameLookup lookup = newNameLookup(owner);
    return findType(
        packageName,
        typeQualifiedName,
        lookup,
        true, // consider secondary types
        progressMonitor);
  }

  /*
   * Internal findType with instanciated name lookup
   */
  IType findType(
      String packageName,
      String typeQualifiedName,
      NameLookup lookup,
      boolean considerSecondaryTypes,
      IProgressMonitor progressMonitor)
      throws JavaModelException {
    NameLookup.Answer answer =
        lookup.findType(
            typeQualifiedName,
            packageName,
            false,
            NameLookup.ACCEPT_ALL,
            considerSecondaryTypes,
            true, // wait for indexes (in case we need to consider secondary types)
            false /*don't check restrictions*/,
            progressMonitor);
    return answer == null ? null : answer.type;
  }

  /*
   * Returns a new name lookup. This name lookup first looks in the working copies of the given owner.
   */
  public NameLookup newNameLookup(WorkingCopyOwner owner) throws JavaModelException {

    JavaModelManager manager = getJavaModelManager();
    ICompilationUnit[] workingCopies =
        owner == null ? null : manager.getWorkingCopies(owner, true /*add primary WCs*/);
    return newNameLookup(workingCopies);
  }

  @Override
  public IPackageFragmentRoot[] getAllPackageFragmentRoots() throws JavaModelException {
    return getAllPackageFragmentRoots(null /*no reverse map*/);
  }

  public IPackageFragmentRoot[] getAllPackageFragmentRoots(Map rootToResolvedEntries)
      throws JavaModelException {

    return computePackageFragmentRoots(
        getResolvedClasspath(), true /*retrieveExportedRoots*/, rootToResolvedEntries);
  }

  /**
   * Returns (local/all) the package fragment roots identified by the given project's classpath.
   * Note: this follows project classpath references to find required project contributions,
   * eliminating duplicates silently. Only works with resolved entries
   *
   * @param resolvedClasspath IClasspathEntry[]
   * @param retrieveExportedRoots boolean
   * @return IPackageFragmentRoot[]
   * @throws JavaModelException
   */
  public IPackageFragmentRoot[] computePackageFragmentRoots(
      IClasspathEntry[] resolvedClasspath, boolean retrieveExportedRoots, Map rootToResolvedEntries)
      throws JavaModelException {

    ObjectVector accumulatedRoots = new ObjectVector();
    computePackageFragmentRoots(
        resolvedClasspath,
        accumulatedRoots,
        new HashSet(5), // rootIDs
        null, // inside original project
        retrieveExportedRoots,
        rootToResolvedEntries);
    IPackageFragmentRoot[] rootArray = new IPackageFragmentRoot[accumulatedRoots.size()];
    accumulatedRoots.copyInto(rootArray);
    return rootArray;
  }

  /**
   * Returns (local/all) the package fragment roots identified by the given project's classpath.
   * Note: this follows project classpath references to find required project contributions,
   * eliminating duplicates silently. Only works with resolved entries
   *
   * @param resolvedClasspath IClasspathEntry[]
   * @param accumulatedRoots ObjectVector
   * @param rootIDs HashSet
   * @param referringEntry project entry referring to this CP or null if initial project
   * @param retrieveExportedRoots boolean
   * @throws JavaModelException
   */
  public void computePackageFragmentRoots(
      IClasspathEntry[] resolvedClasspath,
      ObjectVector accumulatedRoots,
      HashSet rootIDs,
      IClasspathEntry referringEntry,
      boolean retrieveExportedRoots,
      Map rootToResolvedEntries)
      throws JavaModelException {

    if (referringEntry == null) {
      rootIDs.add(rootID());
    }
    for (int i = 0, length = resolvedClasspath.length; i < length; i++) {
      computePackageFragmentRoots(
          resolvedClasspath[i],
          accumulatedRoots,
          rootIDs,
          referringEntry,
          retrieveExportedRoots,
          rootToResolvedEntries);
    }
  }
  /**
   * Reads the classpath file entries of this project's .classpath file. Returns a two-dimensional
   * array, where the number of elements in the row is fixed to 2. The first element is an array of
   * raw classpath entries, which includes the output entry, and the second element is an array of
   * referenced entries that may have been stored by the client earlier. See {@link
   * IJavaProject#getReferencedClasspathEntries()} for more details. As a side effect, unknown
   * elements are stored in the given map (if not null) Throws exceptions if the file cannot be
   * accessed or is malformed.
   */
  public IClasspathEntry[][] readFileEntriesWithException(Map unknownElements)
      throws CoreException, IOException, ClasspathEntry.AssertionFailedException {
    IFile rscFile =
        this.project.getFile(org.eclipse.jdt.internal.core.JavaProject.CLASSPATH_FILENAME);
    byte[] bytes;
    if (rscFile.exists()) {
      bytes = Util.getResourceContentsAsByteArray(rscFile);
    } else {
      // when a project is imported, we get a first delta for the addition of the .project, but the
      // .classpath is not accessible
      // so default to using java.io.File
      // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=96258

      // TODO: keep this comment code for the history
      // TODO: this is original Eclipse code
      //            URI location = rscFile.getLocationURI();
      //            if (location == null)
      //            throw new IOException("Cannot obtain a location URI for " + rscFile);
      // //$NON-NLS-1$
      //            File file = Util.toLocalFile(location, null/*no progress monitor available*/);
      //            if (file == null)
      //                throw new IOException("Unable to fetch file from " + location);
      // //$NON-NLS-1$
      //            try {
      //                bytes =
      // org.eclipse.jdt.internal.compiler.util.Util.getFileByteContent(file);
      //            } catch (IOException e) {
      //                if (!file.exists())
      return new IClasspathEntry[][] {defaultClasspath(), ClasspathEntry.NO_ENTRIES};
      //                throw e;
      //            }
    }
    if (hasUTF8BOM(bytes)) { // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=240034
      int length = bytes.length - IContentDescription.BOM_UTF_8.length;
      System.arraycopy(
          bytes, IContentDescription.BOM_UTF_8.length, bytes = new byte[length], 0, length);
    }
    String xmlClasspath;
    try {
      xmlClasspath =
          new String(
              bytes,
              org.eclipse.jdt.internal.compiler.util.Util
                  .UTF_8); // .classpath always encoded with UTF-8
    } catch (UnsupportedEncodingException e) {
      Util.log(e, "Could not read .classpath with UTF-8 encoding"); // $NON-NLS-1$
      // fallback to default
      xmlClasspath = new String(bytes);
    }
    return decodeClasspath(xmlClasspath, unknownElements);
  }

  /**
   * Reads and decode an XML classpath string. Returns a two-dimensional array, where the number of
   * elements in the row is fixed to 2. The first element is an array of raw classpath entries and
   * the second element is an array of referenced entries that may have been stored by the client
   * earlier. See {@link IJavaProject#getReferencedClasspathEntries()} for more details.
   */
  public IClasspathEntry[][] decodeClasspath(String xmlClasspath, Map unknownElements)
      throws IOException, ClasspathEntry.AssertionFailedException {

    ArrayList paths = new ArrayList();
    IClasspathEntry defaultOutput = null;
    StringReader reader = new StringReader(xmlClasspath);
    Element cpElement;
    try {
      DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
      cpElement = parser.parse(new InputSource(reader)).getDocumentElement();
    } catch (SAXException e) {
      throw new IOException(Messages.file_badFormat);
    } catch (ParserConfigurationException e) {
      throw new IOException(Messages.file_badFormat);
    } finally {
      reader.close();
    }

    if (!cpElement.getNodeName().equalsIgnoreCase("classpath")) { // $NON-NLS-1$
      throw new IOException(Messages.file_badFormat);
    }
    NodeList list = cpElement.getElementsByTagName(ClasspathEntry.TAG_CLASSPATHENTRY);
    int length = list.getLength();

    for (int i = 0; i < length; ++i) {
      Node node = list.item(i);
      if (node.getNodeType() == Node.ELEMENT_NODE) {
        IClasspathEntry entry = ClasspathEntry.elementDecode((Element) node, this, unknownElements);
        if (entry != null) {
          if (entry.getContentKind() == ClasspathEntry.K_OUTPUT) {
            defaultOutput = entry; // separate output
          } else {
            paths.add(entry);
          }
        }
      }
    }
    int pathSize = paths.size();
    IClasspathEntry[][] entries = new IClasspathEntry[2][];
    entries[0] = new IClasspathEntry[pathSize + (defaultOutput == null ? 0 : 1)];
    paths.toArray(entries[0]);
    if (defaultOutput != null) entries[0][pathSize] = defaultOutput; // ensure output is last item

    paths.clear();
    list = cpElement.getElementsByTagName(ClasspathEntry.TAG_REFERENCED_ENTRY);
    length = list.getLength();

    for (int i = 0; i < length; ++i) {
      Node node = list.item(i);
      if (node.getNodeType() == Node.ELEMENT_NODE) {
        IClasspathEntry entry = ClasspathEntry.elementDecode((Element) node, this, unknownElements);
        if (entry != null) {
          paths.add(entry);
        }
      }
    }
    entries[1] = new IClasspathEntry[paths.size()];
    paths.toArray(entries[1]);

    return entries;
  }

  public IClasspathEntry decodeClasspathEntry(String encodedEntry) {

    try {
      if (encodedEntry == null) return null;
      StringReader reader = new StringReader(encodedEntry);
      Element node;

      try {
        DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        node = parser.parse(new InputSource(reader)).getDocumentElement();
      } catch (SAXException e) {
        return null;
      } catch (ParserConfigurationException e) {
        return null;
      } finally {
        reader.close();
      }

      if (!node.getNodeName().equalsIgnoreCase(ClasspathEntry.TAG_CLASSPATHENTRY)
          || node.getNodeType() != Node.ELEMENT_NODE) {
        return null;
      }
      return ClasspathEntry.elementDecode(node, this, null /*not interested in unknown elements*/);
    } catch (IOException e) {
      // bad format
      return null;
    }
  }

  /** Returns a default class path. This is the root of the project */
  protected IClasspathEntry[] defaultClasspath() {

    return new IClasspathEntry[] {JavaCore.newSourceEntry(this.project.getFullPath())};
  }

  public int hashCode() {
    return this.project.hashCode();
  }

  private boolean hasUTF8BOM(byte[] bytes) {
    if (bytes.length > IContentDescription.BOM_UTF_8.length) {
      for (int i = 0, length = IContentDescription.BOM_UTF_8.length; i < length; i++) {
        if (IContentDescription.BOM_UTF_8[i] != bytes[i]) return false;
      }
      return true;
    }
    return false;
  }

  /**
   * Returns the package fragment roots identified by the given entry. In case it refers to a
   * project, it will follow its classpath so as to find exported roots as well. Only works with
   * resolved entry
   *
   * @param resolvedEntry IClasspathEntry
   * @param accumulatedRoots ObjectVector
   * @param rootIDs HashSet
   * @param referringEntry the CP entry (project) referring to this entry, or null if initial
   *     project
   * @param retrieveExportedRoots boolean
   * @throws JavaModelException
   */
  public void computePackageFragmentRoots(
      IClasspathEntry resolvedEntry,
      ObjectVector accumulatedRoots,
      HashSet rootIDs,
      IClasspathEntry referringEntry,
      boolean retrieveExportedRoots,
      Map rootToResolvedEntries)
      throws JavaModelException {

    String rootID = ((ClasspathEntry) resolvedEntry).rootID();
    if (rootIDs.contains(rootID)) return;

    IPath projectPath = this.project.getFullPath();
    IPath entryPath = resolvedEntry.getPath();
    IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
    IPackageFragmentRoot root = null;

    switch (resolvedEntry.getEntryKind()) {

        // source folder
      case IClasspathEntry.CPE_SOURCE:
        if (projectPath.isPrefixOf(entryPath)) {
          Object target = getTarget(entryPath, true /*check existency*/);
          if (target == null) return;

          if (target instanceof IFolder || target instanceof IProject) {
            root = getPackageFragmentRoot((IResource) target);
          }
        }
        break;

        // internal/external JAR or folder
      case IClasspathEntry.CPE_LIBRARY:
        if (referringEntry != null && !resolvedEntry.isExported()) return;
        Object target = getTarget(entryPath, true /*check existency*/);
        if (target == null) return;

        if (target instanceof IResource) {
          //                    // internal target
          root = getPackageFragmentRoot((IResource) target, entryPath);
        } else if (target instanceof File) {
          // external target
          if (isFile(target)) {
            root = new JarPackageFragmentRoot(entryPath, this);
          } else if (((File) target).isDirectory()) {
            //                        root = getPackageFragmentRoot((File)target, entryPath);
            //                        root = new ExternalPackageFragmentRoot(entryPath, this);
            throw new UnsupportedOperationException();
          }
        }
        break;

        // recurse into required project
      case IClasspathEntry.CPE_PROJECT:
        if (!retrieveExportedRoots) return;
        if (referringEntry != null && !resolvedEntry.isExported()) return;
        IResource member = workspaceRoot.findMember(entryPath);
        if (member != null
            && member.getType() == IResource.PROJECT) { // double check if bound to project (23977)
          IProject requiredProjectRsc = (IProject) member;
          if (org.eclipse.jdt.internal.core.JavaProject.hasJavaNature(
              requiredProjectRsc)) { // special builder binary output
            rootIDs.add(rootID);
            org.eclipse.jdt.internal.core.JavaProject requiredProject =
                (org.eclipse.jdt.internal.core.JavaProject) JavaCore.create(requiredProjectRsc);
            requiredProject.computePackageFragmentRoots(
                requiredProject.getResolvedClasspath(),
                accumulatedRoots,
                rootIDs,
                rootToResolvedEntries == null
                    ? resolvedEntry
                    : ((ClasspathEntry) resolvedEntry).combineWith((ClasspathEntry) referringEntry),
                // only combine if need to build the reverse map
                retrieveExportedRoots,
                rootToResolvedEntries);
          }
          break;
        }
    }
    if (root != null) {
      accumulatedRoots.add(root);
      rootIDs.add(rootID);
      if (rootToResolvedEntries != null)
        rootToResolvedEntries.put(
            root, ((ClasspathEntry) resolvedEntry).combineWith((ClasspathEntry) referringEntry));
    }
  }

  /** @see IJavaProject */
  public IPackageFragmentRoot getPackageFragmentRoot(IResource resource) {
    return getPackageFragmentRoot(resource, null /*no entry path*/);
  }

  private IPackageFragmentRoot getPackageFragmentRoot(IResource resource, IPath entryPath) {
    switch (resource.getType()) {
      case IResource.FILE:
        return new JarPackageFragmentRoot(resource, this);
      case IResource.FOLDER:
        //                if
        // (ExternalFoldersManager.isInternalPathForExternalFolder(resource.getFullPath()))
        //                    return new ExternalPackageFragmentRoot(resource, entryPath, this);
        return new PackageFragmentRoot(resource, this);
      case IResource.PROJECT:
        return new PackageFragmentRoot(resource, this);
      default:
        return null;
    }
  }

  /**
   * Answers an ID which is used to distinguish project/entries during package fragment root
   * computations
   *
   * @return String
   */
  public String rootID() {
    return "[PRJ]" + this.project.getFullPath(); // $NON-NLS-1$
  }

  @Override
  public Object[] getNonJavaResources() throws JavaModelException {
    return ((JavaProjectElementInfo) getElementInfo()).getNonJavaResources(this);
  }

  @Override
  public String getOption(String optionName, boolean inheritJavaCoreOptions) {
    return getJavaModelManager()
        .getOption(optionName, inheritJavaCoreOptions, getEclipsePreferences());
  }

  public Map getOptions(boolean inheritJavaCoreOptions) {

    // initialize to the defaults from JavaCore options pool
    Map options = inheritJavaCoreOptions ? JavaCore.getOptions() : new Hashtable(5);

    // Get project specific options
    PerProjectInfo perProjectInfo = null;
    Hashtable projectOptions = null;
    JavaModelManager javaModelManager = getJavaModelManager();
    HashSet optionNames = javaModelManager.optionNames;
    try {
      perProjectInfo = getPerProjectInfo();
      projectOptions = perProjectInfo.options;
      if (projectOptions == null) {
        // get eclipse preferences
        IEclipsePreferences projectPreferences = getEclipsePreferences();
        if (projectPreferences == null) return options; // cannot do better (non-Java project)
        // create project options
        String[] propertyNames = projectPreferences.keys();
        projectOptions = new Hashtable(propertyNames.length);
        for (int i = 0; i < propertyNames.length; i++) {
          String propertyName = propertyNames[i];
          String value = projectPreferences.get(propertyName, null);
          if (value != null) {
            value = value.trim();
            // Keep the option value, even if it's deprecated
            // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=324987
            projectOptions.put(propertyName, value);
            if (!optionNames.contains(propertyName)) {
              // try to migrate deprecated options
              String[] compatibleOptions =
                  (String[]) javaModelManager.deprecatedOptions.get(propertyName);
              if (compatibleOptions != null) {
                for (int co = 0, length = compatibleOptions.length; co < length; co++) {
                  String compatibleOption = compatibleOptions[co];
                  if (!projectOptions.containsKey(compatibleOption))
                    projectOptions.put(compatibleOption, value);
                }
              }
            }
          }
        }
        // cache project options
        perProjectInfo.options = projectOptions;
      }
    } catch (JavaModelException jme) {
      projectOptions = new Hashtable();
    } catch (BackingStoreException e) {
      projectOptions = new Hashtable();
    }

    // Inherit from JavaCore options if specified
    if (inheritJavaCoreOptions) {
      Iterator propertyNames = projectOptions.entrySet().iterator();
      while (propertyNames.hasNext()) {
        Map.Entry entry = (Map.Entry) propertyNames.next();
        String propertyName = (String) entry.getKey();
        String propertyValue = (String) entry.getValue();
        if (propertyValue != null && javaModelManager.knowsOption(propertyName)) {
          options.put(propertyName, propertyValue.trim());
        }
      }
      Util.fixTaskTags(options);
      return options;
    }
    Util.fixTaskTags(projectOptions);
    return projectOptions;
  }

  @Override
  public IPath getOutputLocation() throws JavaModelException {
    return defaultOutputLocation();
  }

  /** Returns a default output location. This is the project bin folder */
  protected IPath defaultOutputLocation() {
    return this.project.getFullPath().append("bin"); // $NON-NLS-1$
  }

  @Override
  public IPackageFragmentRoot getPackageFragmentRoot(String externalLibraryPath) {
    return new JarPackageFragmentRoot(new Path(externalLibraryPath), this);
  }

  @Override
  public IPackageFragmentRoot[] getPackageFragmentRoots() throws JavaModelException {
    Object[] children;
    int length;
    IPackageFragmentRoot[] roots;

    System.arraycopy(
        children = getChildren(),
        0,
        roots = new IPackageFragmentRoot[length = children.length],
        0,
        length);

    return roots;
  }

  @Override
  public IPackageFragmentRoot[] getPackageFragmentRoots(IClasspathEntry entry) {
    throw new UnsupportedOperationException();
  }

  @Override
  public IPackageFragment[] getPackageFragments() throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public IProject getProject() {
    return project;
  }

  @Override
  protected boolean buildStructure(
      OpenableElementInfo info, IProgressMonitor pm, Map newElements, IResource underlyingResource)
      throws JavaModelException {
    // cannot refresh cp markers on opening (emulate cp check on startup) since can create deadlocks
    // (see bug 37274)
    IClasspathEntry[] resolvedClasspath = getResolvedClasspath();

    // compute the pkg fragment roots
    info.setChildren(computePackageFragmentRoots(resolvedClasspath, true, null /*no reverse map*/));
    //        JavaModelManager.getIndexManager().indexAll(project);
    return true;
  }

  /*
   * Returns whether the given resource is accessible through the children or the non-Java resources of this project.
   * Returns true if the resource is not in the project.
   * Assumes that the resource is a folder or a file.
   */
  public boolean contains(IResource resource) {
    return true;
  }

  //    @Override
  //    public IJavaElement getAncestor(int ancestorType) {
  //        return null;
  //    }

  //    @Override
  //    public boolean exists() {
  //        return false;
  //    }

  @Override
  public String getAttachedJavadoc(IProgressMonitor monitor) throws JavaModelException {
    return null;
  }

  @Override
  public IResource getCorrespondingResource() throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public String getElementName() {
    return project.getName();
  }

  /*
   * no path canonicalization
   */
  public IPackageFragmentRoot getPackageFragmentRoot0(IPath externalLibraryPath) {
    //        IFolder linkedFolder =
    // JavaModelManager.getExternalManager().getFolder(externalLibraryPath);
    //        if (linkedFolder != null)
    //            return new ExternalPackageFragmentRoot(linkedFolder, externalLibraryPath, this);
    return new JarPackageFragmentRoot(externalLibraryPath, this);
  }

  /**
   * @param path IPath
   * @return A handle to the package fragment root identified by the given path. This method is
   *     handle-only and the element may or may not exist. Returns <code>null</code> if unable to
   *     generate a handle from the path (for example, an absolute path that has less than 1
   *     segment. The path may be relative or absolute.
   */
  public IPackageFragmentRoot getPackageFragmentRoot(IPath path) {
    if (!path.isAbsolute()) {
      path = getPath().append(path);
    }
    int segmentCount = path.segmentCount();
    if (segmentCount == 0) {
      return null;
    }
    if (path.getDevice() != null
        || JavaModel.getExternalTarget(path, true /*check existence*/) != null) {
      // external path
      return getPackageFragmentRoot0(path);
    }
    IWorkspaceRoot workspaceRoot = this.project.getWorkspace().getRoot();
    IResource resource = workspaceRoot.findMember(path);
    if (resource == null) {
      // resource doesn't exist in workspace
      if (path.getFileExtension() != null) {
        if (!workspaceRoot.getProject(path.segment(0)).exists()) {
          // assume it is an external ZIP archive
          return getPackageFragmentRoot0(path);
        } else {
          // assume it is an internal ZIP archive
          resource = workspaceRoot.getFile(path);
        }
      } else if (segmentCount == 1) {
        // assume it is a project
        String projectName = path.segment(0);
        if (getElementName()
            .equals(projectName)) { // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=75814
          // default root
          resource = this.project;
        } else {
          // lib being another project
          resource = workspaceRoot.getProject(projectName);
        }
      } else {
        // assume it is an internal folder
        resource = workspaceRoot.getFolder(path);
      }
    }
    return getPackageFragmentRoot(resource);
  }

  @Override
  public IJavaElement getHandleFromMemento(
      String token, MementoTokenizer memento, WorkingCopyOwner owner) {
    switch (token.charAt(0)) {
      case JavaElement.JEM_PACKAGEFRAGMENTROOT:
        String rootPath = IPackageFragmentRoot.DEFAULT_PACKAGEROOT_PATH;
        token = null;
        while (memento.hasMoreTokens()) {
          token = memento.nextToken();
          // https://bugs.eclipse.org/bugs/show_bug.cgi?id=331821
          if (token == MementoTokenizer.PACKAGEFRAGMENT || token == MementoTokenizer.COUNT) {
            break;
          }
          rootPath += token;
        }
        JavaElement root = (JavaElement) getPackageFragmentRoot(new Path(rootPath));
        if (token != null && token.charAt(0) == JavaElement.JEM_PACKAGEFRAGMENT) {
          return root.getHandleFromMemento(token, memento, owner);
        } else {
          return root.getHandleFromMemento(memento, owner);
        }
    }
    return null;
  }

  @Override
  public int getElementType() {
    return IJavaElement.JAVA_PROJECT;
  }

  @Override
  protected char getHandleMementoDelimiter() {
    return JavaElement.JEM_JAVAPROJECT;
  }

  public IPath getPath() {
    return project.getFullPath();
  }

  @Override
  public IJavaElement getPrimaryElement() {
    throw new UnsupportedOperationException();
  }

  @Override
  protected IResource resource(PackageFragmentRoot root) {
    return project;
  }

  @Override
  public ISchedulingRule getSchedulingRule() {
    throw new UnsupportedOperationException();
  }

  @Override
  public IResource getUnderlyingResource() throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean isReadOnly() {
    return false;
  }

  @Override
  public Object getAdapter(Class aClass) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void close() throws JavaModelException {
    super.close();
  }

  @Override
  public String findRecommendedLineSeparator() throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public IBuffer getBuffer() throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean hasUnsavedChanges() throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  //    @Override
  //    public boolean isOpen() {
  //        throw new UnsupportedOperationException();
  //    }

  @Override
  public void makeConsistent(IProgressMonitor progress) throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void open(IProgressMonitor progress) throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void save(IProgressMonitor progress, boolean force) throws JavaModelException {
    throw new UnsupportedOperationException();
  }

  /**
   * The path is known to match a source/library folder entry.
   *
   * @param path IPath
   * @return IPackageFragmentRoot
   */
  public IPackageFragmentRoot getFolderPackageFragmentRoot(IPath path) {
    if (path.segmentCount() == 1) { // default project root
      return getPackageFragmentRoot();
    }
    return getPackageFragmentRoot(this.project.getWorkspace().getRoot().getFolder(path));
  }

  public JavaProjectElementInfo.ProjectCache getProjectCache() throws JavaModelException {
    return ((JavaProjectElementInfo) getElementInfo()).getProjectCache(this);
  }

  /** Returns a new element info for this element. */
  protected Object createElementInfo() {
    return new JavaProjectElementInfo();
  }

  @Override
  protected IStatus validateExistence(IResource underlyingResource) {
    if ((!((IProject) underlyingResource).getFolder(INNER_DIR).exists())) {
      return newDoesNotExistStatus();
    }
    return JavaModelStatus.VERIFIED_OK;
  }

  protected JavaModelStatus newDoesNotExistStatus() {
    return new JavaModelStatus(IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST, this);
  }

  //    /**
  //     * @see JavaElement#getHandleMemento(StringBuffer)
  //     */
  //    protected void getHandleMemento(StringBuffer buff) {
  ////        buff.append(getElementName());
  //    }

  /*
   * Resets this project's caches
   */
  public void resetCaches() {
    JavaProjectElementInfo info = (JavaProjectElementInfo) getJavaModelManager().peekAtInfo(this);
    if (info != null) {
      info.resetCaches();
    }
  }

  private void addToResult(
      IClasspathEntry rawEntry,
      IClasspathEntry resolvedEntry,
      ResolvedClasspath result,
      LinkedHashSet resolvedEntries,
      Map oldChainedEntriesMap,
      boolean addAsChainedEntry,
      Map knownDrives) {

    IPath resolvedPath;
    // If it's already been resolved, do not add to resolvedEntries
    if (result.rawReverseMap.get(resolvedPath = resolvedEntry.getPath()) == null) {
      result.rawReverseMap.put(resolvedPath, rawEntry);
      result.rootPathToResolvedEntries.put(resolvedPath, resolvedEntry);
      resolvedEntries.add(resolvedEntry);
      if (addAsChainedEntry) {
        IClasspathEntry chainedEntry = null;
        chainedEntry = (ClasspathEntry) oldChainedEntriesMap.get(resolvedPath);
        if (chainedEntry != null) {
          // This is required to keep the attributes if any added by the user in
          // the previous session such as source attachment path etc.
          copyFromOldChainedEntry((ClasspathEntry) resolvedEntry, (ClasspathEntry) chainedEntry);
        }
      }
    }
    //        if (resolvedEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY &&/*
    // ExternalFoldersManager.isExternalFolderPath(resolvedPath)*/ false) {
    //            externalFoldersManager.addFolder(resolvedPath, true/*scheduleForCreation*/); //
    // no-op if not an external folder or if already registered
    //        }
    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=336046
    //        // The source attachment path could be external too and in which case, must be added.
    //        IPath sourcePath = resolvedEntry.getSourceAttachmentPath();
    //        if (sourcePath != null && driveExists(sourcePath, knownDrives) &&
    // ExternalFoldersManager.isExternalFolderPath(sourcePath)) {
    //            externalFoldersManager.addFolder(sourcePath, true);
    //        }
  }

  private void copyFromOldChainedEntry(ClasspathEntry resolvedEntry, ClasspathEntry chainedEntry) {
    IPath path = chainedEntry.getSourceAttachmentPath();
    if (path != null) {
      resolvedEntry.sourceAttachmentPath = path;
    }
    path = chainedEntry.getSourceAttachmentRootPath();
    if (path != null) {
      resolvedEntry.sourceAttachmentRootPath = path;
    }
    IClasspathAttribute[] attributes = chainedEntry.getExtraAttributes();
    if (attributes != null) {
      resolvedEntry.extraAttributes = attributes;
    }
  }

  /** Convenience method that returns the specific type of info for a Java project. */
  protected JavaProjectElementInfo getJavaProjectElementInfo() throws JavaModelException {

    return (JavaProjectElementInfo) getElementInfo();
  }

  public NameLookup newNameLookup(ICompilationUnit[] workingCopies) throws JavaModelException {
    return getJavaProjectElementInfo().newNameLookup(this, workingCopies);
  }

  public SearchableEnvironment newSearchableNameEnvironment(ICompilationUnit[] workingCopies)
      throws JavaModelException {
    return new SearchableEnvironment(this, workingCopies);
  }

  /**
   * Returns true if this handle represents the same Java project as the given handle. Two handles
   * represent the same project if they are identical or if they represent a project with the same
   * underlying resource and occurrence counts.
   *
   * @see JavaElement#equals(Object)
   */
  public boolean equals(Object o) {
    if (this == o) return true;

    if (!(o instanceof org.eclipse.jdt.internal.core.JavaProject)) return false;

    org.eclipse.jdt.internal.core.JavaProject other = (org.eclipse.jdt.internal.core.JavaProject) o;
    return this.project.equals(other.getProject());
  }

  public PerProjectInfo getPerProjectInfo() throws JavaModelException {
    return getJavaModelManager().getPerProjectInfoCheckExistence(this.project);
  }
  /*
   * Returns a new search name environment for this project. This name environment first looks in the working copies
   * of the given owner.
   */
  public SearchableEnvironment newSearchableNameEnvironment(WorkingCopyOwner owner)
      throws JavaModelException {
    return new SearchableEnvironment(this, owner);
  }

  /**
   * This is a helper method returning the expanded classpath for the project, as a list of
   * classpath entries, where all classpath variable entries have been resolved and substituted with
   * their final target entries. All project exports have been appended to project entries.
   *
   * @return IClasspathEntry[]
   * @throws JavaModelException
   */
  public IClasspathEntry[] getExpandedClasspath() throws JavaModelException {

    //        ObjectVector accumulatedEntries = new ObjectVector();
    //        computeExpandedClasspath(null, new HashSet(5), accumulatedEntries);
    //
    //        IClasspathEntry[] expandedPath = new IClasspathEntry[accumulatedEntries.size()];
    //        accumulatedEntries.copyInto(expandedPath);
    //
    //        return expandedPath;
    return getResolvedClasspath();
  }

  /**
   * Returns the project custom preference pool. Project preferences may include custom encoding.
   *
   * @return IEclipsePreferences or <code>null</code> if the project does not have a java nature.
   */
  public IEclipsePreferences getEclipsePreferences() {
    if (!org.eclipse.jdt.internal.core.JavaProject.hasJavaNature(this.project)) return null;
    // Get cached preferences if exist
    PerProjectInfo perProjectInfo = getJavaModelManager().getPerProjectInfo(this.project, true);
    if (perProjectInfo.preferences != null) return perProjectInfo.preferences;
    // Init project preferences
    IScopeContext context = new ProjectScope(getProject());
    final IEclipsePreferences eclipsePreferences = context.getNode(JavaCore.PLUGIN_ID);
    //        updatePreferences(eclipsePreferences);
    perProjectInfo.preferences = eclipsePreferences;

    //        // Listen to new preferences node
    //        final IEclipsePreferences eclipseParentPreferences = (IEclipsePreferences)
    // eclipsePreferences.parent();
    //        if (eclipseParentPreferences != null) {
    //            if (this.preferencesNodeListener != null) {
    //
    // eclipseParentPreferences.removeNodeChangeListener(this.preferencesNodeListener);
    //            }
    //            this.preferencesNodeListener = new IEclipsePreferences.INodeChangeListener() {
    //                public void added(IEclipsePreferences.NodeChangeEvent event) {
    //                    // do nothing
    //                }
    //                public void removed(IEclipsePreferences.NodeChangeEvent event) {
    //                    if (event.getChild() == eclipsePreferences) {
    //
    // JavaModelManager.getJavaModelManager().resetProjectPreferences(JavaProject.this);
    //                    }
    //                }
    //            };
    //            eclipseParentPreferences.addNodeChangeListener(this.preferencesNodeListener);
    //        }

    //        // Listen to preferences changes
    //        if (this.preferencesChangeListener != null) {
    //            eclipsePreferences.removePreferenceChangeListener(this.preferencesChangeListener);
    //        }
    //        this.preferencesChangeListener = new IEclipsePreferences.IPreferenceChangeListener() {
    //            public void preferenceChange(IEclipsePreferences.PreferenceChangeEvent event) {
    //                String propertyName = event.getKey();
    //                JavaModelManager manager = JavaModelManager.getJavaModelManager();
    //                if (propertyName.startsWith(JavaCore.PLUGIN_ID)) {
    //                    if (propertyName.equals(JavaCore.CORE_JAVA_BUILD_CLEAN_OUTPUT_FOLDER) ||
    //                        propertyName.equals(JavaCore.CORE_JAVA_BUILD_RESOURCE_COPY_FILTER) ||
    //                        propertyName.equals(JavaCore.CORE_JAVA_BUILD_DUPLICATE_RESOURCE) ||
    //
    // propertyName.equals(JavaCore.CORE_JAVA_BUILD_RECREATE_MODIFIED_CLASS_FILES_IN_OUTPUT_FOLDER)
    // ||
    //                        propertyName.equals(JavaCore.CORE_JAVA_BUILD_INVALID_CLASSPATH) ||
    //                        propertyName.equals(JavaCore.CORE_ENABLE_CLASSPATH_EXCLUSION_PATTERNS)
    // ||
    //
    // propertyName.equals(JavaCore.CORE_ENABLE_CLASSPATH_MULTIPLE_OUTPUT_LOCATIONS) ||
    //                        propertyName.equals(JavaCore.CORE_INCOMPLETE_CLASSPATH) ||
    //                        propertyName.equals(JavaCore.CORE_CIRCULAR_CLASSPATH) ||
    //
    // propertyName.equals(JavaCore.CORE_OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE) ||
    //                        propertyName.equals(JavaCore.CORE_INCOMPATIBLE_JDK_LEVEL) ||
    //                        propertyName.equals(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM))
    //                    {
    //                        manager.deltaState.addClasspathValidation(JavaProject.this);
    //                    }
    //                    manager.resetProjectOptions(JavaProject.this);
    //                    JavaProject.this.resetCaches(); // see
    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=233568
    //                }
    //            }
    //        };
    //        eclipsePreferences.addPreferenceChangeListener(this.preferencesChangeListener);
    return eclipsePreferences;
  }

  /**
   * Returns a canonicalized path from the given external path. Note that the return path contains
   * the same number of segments and it contains a device only if the given path contained one.
   *
   * @param externalPath IPath
   * @see java.io.File for the definition of a canonicalized path
   * @return IPath
   */
  public static IPath canonicalizedPath(IPath externalPath) {

    if (externalPath == null) return null;

    if (IS_CASE_SENSITIVE) {
      return externalPath;
    }

    // if not external path, return original path
    IWorkspace workspace = ResourcesPlugin.getWorkspace();
    if (workspace == null) return externalPath; // protection during shutdown (30487)
    if (workspace.getRoot().findMember(externalPath) != null) {
      return externalPath;
    }

    IPath canonicalPath = null;
    try {
      canonicalPath = new Path(new File(externalPath.toOSString()).getCanonicalPath());
    } catch (IOException e) {
      // default to original path
      return externalPath;
    }

    IPath result;
    int canonicalLength = canonicalPath.segmentCount();
    if (canonicalLength == 0) {
      // the java.io.File canonicalization failed
      return externalPath;
    } else if (externalPath.isAbsolute()) {
      result = canonicalPath;
    } else {
      // if path is relative, remove the first segments that were added by the java.io.File
      // canonicalization
      // e.g. 'lib/classes.zip' was converted to 'd:/myfolder/lib/classes.zip'
      int externalLength = externalPath.segmentCount();
      if (canonicalLength >= externalLength) {
        result = canonicalPath.removeFirstSegments(canonicalLength - externalLength);
      } else {
        return externalPath;
      }
    }

    // keep device only if it was specified (this is because File.getCanonicalPath() converts
    // '/lib/classes.zip' to 'd:/lib/classes/zip')
    if (externalPath.getDevice() == null) {
      result = result.setDevice(null);
    }
    // keep trailing separator only if it was specified (this is because File.getCanonicalPath()
    // converts 'd:/lib/classes/' to 'd:/lib/classes')
    if (externalPath.hasTrailingSeparator()) {
      result = result.addTrailingSeparator();
    }
    return result;
  }

  /** Remove all markers denoting classpath problems */
  // TODO (philippe) should improve to use a bitmask instead of booleans (CYCLE, FORMAT, VALID)
  protected void flushClasspathProblemMarkers(
      boolean flushCycleMarkers,
      boolean flushClasspathFormatMarkers,
      boolean flushOverlappingOutputMarkers) {
    //        try {
    //            if (this.project.isAccessible()) {
    //                IMarker[] markers =
    // this.project.findMarkers(IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, false,
    // IResource.DEPTH_ZERO);
    //                for (int i = 0, length = markers.length; i < length; i++) {
    //                    IMarker marker = markers[i];
    //                    if (flushCycleMarkers && flushClasspathFormatMarkers &&
    // flushOverlappingOutputMarkers) {
    //                        marker.delete();
    //                    } else {
    //                        String cycleAttr =
    // (String)marker.getAttribute(IJavaModelMarker.CYCLE_DETECTED);
    //                        String classpathFileFormatAttr =
    // (String)marker.getAttribute(IJavaModelMarker.CLASSPATH_FILE_FORMAT);
    //                        String overlappingOutputAttr = (String)
    // marker.getAttribute(IJavaModelMarker.OUTPUT_OVERLAPPING_SOURCE);
    //                        if ((flushCycleMarkers == (cycleAttr != null &&
    // cycleAttr.equals("true"))) //$NON-NLS-1$
    //                            && (flushOverlappingOutputMarkers == (overlappingOutputAttr !=
    // null && overlappingOutputAttr.equals("true"))) //$NON-NLS-1$
    //                            && (flushClasspathFormatMarkers == (classpathFileFormatAttr !=
    // null && classpathFileFormatAttr.equals("true")))){ //$NON-NLS-1$
    //                            marker.delete();
    //                        }
    //                    }
    //                }
    //            }
    //        } catch (CoreException e) {
    //            // could not flush markers: not much we can do
    //            if (JavaModelManager.VERBOSE) {
    //                e.printStackTrace();
    //            }
    //        }
  }

  /**
   * Writes the classpath in a sharable format (VCM-wise) only when necessary, that is, if it is
   * semantically different from the existing one in file. Will never write an identical one.
   *
   * @param newClasspath IClasspathEntry[]
   * @param newOutputLocation IPath
   * @return boolean Return whether the .classpath file was modified.
   * @throws JavaModelException
   */
  public boolean writeFileEntries(
      IClasspathEntry[] newClasspath, IClasspathEntry[] referencedEntries, IPath newOutputLocation)
      throws JavaModelException {

    if (!this.project.isAccessible()) return false;

    Map unknownElements = new HashMap();
    IClasspathEntry[][] fileEntries = readFileEntries(unknownElements);
    if (fileEntries[0] != JavaProject.INVALID_CLASSPATH
        && areClasspathsEqual(newClasspath, newOutputLocation, fileEntries[0])
        && (referencedEntries == null || areClasspathsEqual(referencedEntries, fileEntries[1]))) {
      // no need to save it, it is the same
      return false;
    }

    // actual file saving
    try {
      setSharedProperty(
          JavaProject.CLASSPATH_FILENAME,
          encodeClasspath(
              newClasspath, referencedEntries, newOutputLocation, true, unknownElements));
      return true;
    } catch (CoreException e) {
      throw new JavaModelException(e);
    }
  }

  /**
   * Record a shared persistent property onto a project. Note that it is orthogonal to IResource
   * persistent properties, and client code has to decide which form of storage to use
   * appropriately. Shared properties produce real resource files which can be shared through a VCM
   * onto a server. Persistent properties are not shareable.
   *
   * <p>Shared properties end up in resource files, and thus cannot be modified during delta
   * notifications (a CoreException would then be thrown).
   *
   * @param key String
   * @param value String see JavaProject#getSharedProperty(String key)
   * @throws CoreException
   */
  public void setSharedProperty(String key, String value) throws CoreException {

    IFile rscFile = this.project.getFile(key);
    byte[] bytes = null;
    try {
      bytes =
          value.getBytes(
              org.eclipse.jdt.internal.compiler.util.Util
                  .UTF_8); // .classpath always encoded with UTF-8
    } catch (UnsupportedEncodingException e) {
      Util.log(e, "Could not write .classpath with UTF-8 encoding "); // $NON-NLS-1$
      // fallback to default
      bytes = value.getBytes();
    }
    InputStream inputStream = new ByteArrayInputStream(bytes);
    // update the resource content
    if (rscFile.exists()) {
      //            if (rscFile.isReadOnly()) {
      //                // provide opportunity to checkout read-only .classpath file (23984)
      //                ResourcesPlugin.getWorkspace().validateEdit(new IFile[]{rscFile},
      // IWorkspace.VALIDATE_PROMPT);
      //            }
      rscFile.setContents(inputStream, IResource.FORCE, null);
    } else {
      rscFile.create(inputStream, IResource.FORCE, null);
    }
  }

  private static boolean areClasspathsEqual(IClasspathEntry[] first, IClasspathEntry[] second) {
    if (first != second) {
      if (first == null) return false;
      int length = first.length;
      if (second == null || second.length != length) return false;
      for (int i = 0; i < length; i++) {
        if (!first[i].equals(second[i])) return false;
      }
    }
    return true;
  }

  /**
   * Compare current classpath with given one to see if any different. Note that the argument
   * classpath contains its binary output.
   *
   * @param newClasspath IClasspathEntry[]
   * @param newOutputLocation IPath
   * @param otherClasspathWithOutput IClasspathEntry[]
   * @return boolean
   */
  private static boolean areClasspathsEqual(
      IClasspathEntry[] newClasspath,
      IPath newOutputLocation,
      IClasspathEntry[] otherClasspathWithOutput) {

    if (otherClasspathWithOutput == null || otherClasspathWithOutput.length == 0) return false;

    int length = otherClasspathWithOutput.length;
    if (length != newClasspath.length + 1)
      // output is amongst file entries (last one)
      return false;

    // compare classpath entries
    for (int i = 0; i < length - 1; i++) {
      if (!otherClasspathWithOutput[i].equals(newClasspath[i])) return false;
    }
    // compare binary outputs
    IClasspathEntry output = otherClasspathWithOutput[length - 1];
    if (output.getContentKind() != ClasspathEntry.K_OUTPUT
        || !output.getPath().equals(newOutputLocation)) return false;
    return true;
  }

  /** Returns the XML String encoding of the class path. */
  protected String encodeClasspath(
      IClasspathEntry[] classpath,
      IClasspathEntry[] referencedEntries,
      IPath outputLocation,
      boolean indent,
      Map unknownElements)
      throws JavaModelException {
    try {
      ByteArrayOutputStream s = new ByteArrayOutputStream();
      OutputStreamWriter writer = new OutputStreamWriter(s, "UTF8"); // $NON-NLS-1$
      XMLWriter xmlWriter = new XMLWriter(writer, this, true /*print XML version*/);

      xmlWriter.startTag(ClasspathEntry.TAG_CLASSPATH, indent);
      for (int i = 0; i < classpath.length; ++i) {
        ((ClasspathEntry) classpath[i])
            .elementEncode(
                xmlWriter, this.project.getFullPath(), indent, true, unknownElements, false);
      }

      if (outputLocation != null) {
        outputLocation = outputLocation.removeFirstSegments(1);
        outputLocation = outputLocation.makeRelative();
        HashMap parameters = new HashMap();
        parameters.put(
            ClasspathEntry.TAG_KIND, ClasspathEntry.kindToString(ClasspathEntry.K_OUTPUT));
        parameters.put(ClasspathEntry.TAG_PATH, String.valueOf(outputLocation));
        xmlWriter.printTag(ClasspathEntry.TAG_CLASSPATHENTRY, parameters, indent, true, true);
      }

      if (referencedEntries != null) {
        for (int i = 0; i < referencedEntries.length; ++i) {
          ((ClasspathEntry) referencedEntries[i])
              .elementEncode(
                  xmlWriter, this.project.getFullPath(), indent, true, unknownElements, true);
        }
      }

      xmlWriter.endTag(ClasspathEntry.TAG_CLASSPATH, indent, true /*insert new line*/);
      writer.flush();
      writer.close();
      return s.toString("UTF8"); // $NON-NLS-1$
    } catch (IOException e) {
      throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION);
    }
  }

  public String encodeClasspathEntry(IClasspathEntry classpathEntry) {
    try {
      ByteArrayOutputStream s = new ByteArrayOutputStream();
      OutputStreamWriter writer = new OutputStreamWriter(s, "UTF8"); // $NON-NLS-1$
      XMLWriter xmlWriter = new XMLWriter(writer, this, false /*don't print XML version*/);

      ((ClasspathEntry) classpathEntry)
          .elementEncode(
              xmlWriter,
              this.project.getFullPath(),
              true /*indent*/,
              true /*insert new line*/,
              null /*not interested in unknown elements*/,
              (classpathEntry.getReferencingEntry() != null));

      writer.flush();
      writer.close();
      return s.toString("UTF8"); // $NON-NLS-1$
    } catch (IOException e) {
      return null; // never happens since all is done in memory
    }
  }

  /*
   * Reads the classpath file entries of this project's .classpath file.
   * This includes the output entry.
   * As a side effect, unknown elements are stored in the given map (if not null)
   */
  private IClasspathEntry[][] readFileEntries(Map unkwownElements) {
    try {
      return readFileEntriesWithException(unkwownElements);
    } catch (CoreException e) {
      Util.log(
          e,
          "Exception while reading "
              + getPath().append(JavaProject.CLASSPATH_FILENAME)); // $NON-NLS-1$
      return new IClasspathEntry[][] {JavaProject.INVALID_CLASSPATH, ClasspathEntry.NO_ENTRIES};
    } catch (IOException e) {
      Util.log(
          e,
          "Exception while reading "
              + getPath().append(JavaProject.CLASSPATH_FILENAME)); // $NON-NLS-1$
      return new IClasspathEntry[][] {JavaProject.INVALID_CLASSPATH, ClasspathEntry.NO_ENTRIES};
    } catch (ClasspathEntry.AssertionFailedException e) {
      Util.log(
          e,
          "Exception while reading "
              + getPath().append(JavaProject.CLASSPATH_FILENAME)); // $NON-NLS-1$
      return new IClasspathEntry[][] {JavaProject.INVALID_CLASSPATH, ClasspathEntry.NO_ENTRIES};
    }
  }

  public static class ResolvedClasspath {
    IClasspathEntry[] resolvedClasspath;
    IJavaModelStatus unresolvedEntryStatus = JavaModelStatus.VERIFIED_OK;
    HashMap<IPath, IClasspathEntry> rawReverseMap = new HashMap<>();
    Map<IPath, IClasspathEntry> rootPathToResolvedEntries = new HashMap<>();
    IClasspathEntry[] referencedEntries = null;
  }
}
